// Copyright 2012 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_MACRO_ASSEMBLER_H_
#define V8_MACRO_ASSEMBLER_H_

#include "src/frames.h"
#include "src/heap/heap.h"
#include "src/turbo-assembler.h"

// Helper types to make boolean flag easier to read at call-site.
enum InvokeFlag {
    CALL_FUNCTION,
    JUMP_FUNCTION
};

// Flags used for the AllocateInNewSpace functions.
enum AllocationFlags {
    // No special flags.
    NO_ALLOCATION_FLAGS = 0,
    // The content of the result register already contains the allocation top in
    // new space.
    RESULT_CONTAINS_TOP = 1 << 0,
    // Specify that the requested size of the space to allocate is specified in
    // words instead of bytes.
    SIZE_IN_WORDS = 1 << 1,
    // Align the allocation to a multiple of kDoubleSize
    DOUBLE_ALIGNMENT = 1 << 2,
    // Directly allocate in old space
    PRETENURE = 1 << 3,
};

// This is the only place allowed to include the platform-specific headers.
#define INCLUDED_FROM_MACRO_ASSEMBLER_H
#if V8_TARGET_ARCH_IA32
#include "src/ia32/macro-assembler-ia32.h"
#elif V8_TARGET_ARCH_X64
#include "src/x64/macro-assembler-x64.h"
#elif V8_TARGET_ARCH_ARM64
#include "src/arm64/constants-arm64.h"
#include "src/arm64/macro-assembler-arm64.h"
#elif V8_TARGET_ARCH_ARM
#include "src/arm/constants-arm.h"
#include "src/arm/macro-assembler-arm.h"
#elif V8_TARGET_ARCH_PPC
#include "src/ppc/constants-ppc.h"
#include "src/ppc/macro-assembler-ppc.h"
#elif V8_TARGET_ARCH_MIPS
#include "src/mips/constants-mips.h"
#include "src/mips/macro-assembler-mips.h"
#elif V8_TARGET_ARCH_MIPS64
#include "src/mips64/constants-mips64.h"
#include "src/mips64/macro-assembler-mips64.h"
#elif V8_TARGET_ARCH_S390
#include "src/s390/constants-s390.h"
#include "src/s390/macro-assembler-s390.h"
#else
#error Unsupported target architecture.
#endif
#undef INCLUDED_FROM_MACRO_ASSEMBLER_H

namespace v8 {
namespace internal {

    // Simulators only support C calls with up to kMaxCParameters parameters.
    static constexpr int kMaxCParameters = 9;

    class FrameScope {
    public:
        explicit FrameScope(TurboAssembler* tasm, StackFrame::Type type)
            : tasm_(tasm)
            , type_(type)
            , old_has_frame_(tasm->has_frame())
        {
            tasm->set_has_frame(true);
            if (type != StackFrame::MANUAL && type_ != StackFrame::NONE) {
                tasm->EnterFrame(type);
            }
        }

        ~FrameScope()
        {
            if (type_ != StackFrame::MANUAL && type_ != StackFrame::NONE) {
                tasm_->LeaveFrame(type_);
            }
            tasm_->set_has_frame(old_has_frame_);
        }

        // Normally we generate the leave-frame code when this object goes
        // out of scope.  Sometimes we may need to generate the code somewhere else
        // in addition.  Calling this will achieve that, but the object stays in
        // scope, the MacroAssembler is still marked as being in a frame scope, and
        // the code will be generated again when it goes out of scope.
        void GenerateLeaveFrame()
        {
            DCHECK(type_ != StackFrame::MANUAL && type_ != StackFrame::NONE);
            tasm_->LeaveFrame(type_);
        }

    private:
        TurboAssembler* tasm_;
        StackFrame::Type type_;
        bool old_has_frame_;
    };

    class FrameAndConstantPoolScope {
    public:
        FrameAndConstantPoolScope(MacroAssembler* masm, StackFrame::Type type)
            : masm_(masm)
            , type_(type)
            , old_has_frame_(masm->has_frame())
            , old_constant_pool_available_(FLAG_enable_embedded_constant_pool && masm->is_constant_pool_available())
        {
            masm->set_has_frame(true);
            if (FLAG_enable_embedded_constant_pool) {
                masm->set_constant_pool_available(true);
            }
            if (type_ != StackFrame::MANUAL && type_ != StackFrame::NONE) {
                masm->EnterFrame(type, !old_constant_pool_available_);
            }
        }

        ~FrameAndConstantPoolScope()
        {
            masm_->LeaveFrame(type_);
            masm_->set_has_frame(old_has_frame_);
            if (FLAG_enable_embedded_constant_pool) {
                masm_->set_constant_pool_available(old_constant_pool_available_);
            }
        }

        // Normally we generate the leave-frame code when this object goes
        // out of scope.  Sometimes we may need to generate the code somewhere else
        // in addition.  Calling this will achieve that, but the object stays in
        // scope, the MacroAssembler is still marked as being in a frame scope, and
        // the code will be generated again when it goes out of scope.
        void GenerateLeaveFrame()
        {
            DCHECK(type_ != StackFrame::MANUAL && type_ != StackFrame::NONE);
            masm_->LeaveFrame(type_);
        }

    private:
        MacroAssembler* masm_;
        StackFrame::Type type_;
        bool old_has_frame_;
        bool old_constant_pool_available_;

        DISALLOW_IMPLICIT_CONSTRUCTORS(FrameAndConstantPoolScope);
    };

    // Class for scoping the the unavailability of constant pool access.
    class ConstantPoolUnavailableScope {
    public:
        explicit ConstantPoolUnavailableScope(Assembler* assembler)
            : assembler_(assembler)
            , old_constant_pool_available_(FLAG_enable_embedded_constant_pool && assembler->is_constant_pool_available())
        {
            if (FLAG_enable_embedded_constant_pool) {
                assembler->set_constant_pool_available(false);
            }
        }
        ~ConstantPoolUnavailableScope()
        {
            if (FLAG_enable_embedded_constant_pool) {
                assembler_->set_constant_pool_available(old_constant_pool_available_);
            }
        }

    private:
        Assembler* assembler_;
        int old_constant_pool_available_;

        DISALLOW_IMPLICIT_CONSTRUCTORS(ConstantPoolUnavailableScope);
    };

    class AllowExternalCallThatCantCauseGC : public FrameScope {
    public:
        explicit AllowExternalCallThatCantCauseGC(MacroAssembler* masm)
            : FrameScope(masm, StackFrame::NONE)
        {
        }
    };

    // Prevent the use of the RootArray during the lifetime of this
    // scope object.
    class NoRootArrayScope {
    public:
        explicit NoRootArrayScope(TurboAssembler* masm)
            : masm_(masm)
            , old_value_(masm->root_array_available())
        {
            masm->set_root_array_available(false);
        }

        ~NoRootArrayScope() { masm_->set_root_array_available(old_value_); }

    private:
        TurboAssembler* masm_;
        bool old_value_;
    };

    // Wrapper class for passing expected and actual parameter counts as
    // either registers or immediate values. Used to make sure that the
    // caller provides exactly the expected number of parameters to the
    // callee.
    class ParameterCount {
    public:
        explicit ParameterCount(Register reg)
            : reg_(reg)
            , immediate_(0)
        {
        }
        explicit ParameterCount(uint16_t imm)
            : reg_(no_reg)
            , immediate_(imm)
        {
        }

        bool is_reg() const { return reg_.is_valid(); }
        bool is_immediate() const { return !is_reg(); }

        Register reg() const
        {
            DCHECK(is_reg());
            return reg_;
        }
        uint16_t immediate() const
        {
            DCHECK(is_immediate());
            return immediate_;
        }

    private:
        const Register reg_;
        const uint16_t immediate_;

        DISALLOW_IMPLICIT_CONSTRUCTORS(ParameterCount);
    };

} // namespace internal
} // namespace v8

#endif // V8_MACRO_ASSEMBLER_H_
